home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / frcp < prev    next >
Encoding:
Korn shell script  |  1997-08-26  |  13.9 KB  |  455 lines

  1. #!/bin/ksh
  2. # @(#) frcp.ksh 2.8 97/06/06
  3. # 92/06/29 john h. dubois iii (john@armory.com)
  4. # 92/10/14 Cleaned up, improved, added -d and -r options
  5. # 92/11/11 Made work with a dest of '.'
  6. # 93/07/09 Added -l and -n options, & login as anonymous if no .netrc entry
  7. # 93/11/14 Use either passwd or password in .netrc, since ftp does.
  8. # 94/10/05 Changed local machine name to that returned by hostname instead
  9. #          of uname, since it's used in anonymous ftp password and some sites
  10. #          want a FQDN.
  11. # 94/10/16 Added -i to ftp args.  Do not lcd when mgetting.
  12. # 95/11/02 Strip trailing . from hostname used as password for anonymous ftp;
  13. #          some ftpds don't like that.
  14. # 96/03/08 If -D is given and dest is remote, create dirs on remote machine
  15. # 96/05/22 Added support for ftp URLs.
  16. # 96/06/11 Added sw options.
  17. # 96/07/17 Remove any trailing : and 23 from machine name given in FTP URLs.
  18. # 96/12/18 Added gtDL options.
  19. # 96/12/24 Added px options.
  20. # 97/04/01 Make coprocess output go to stderr.
  21. # 97/06/06 Rewrote .netrc parsing to make it more like ftp
  22.  
  23. # frcp: ftp front end with rcp-like syntax.
  24. # Note: requires machine names given to be listed with
  25. #       user and password in .netrc; if not, login "anonymous" is used.
  26.  
  27. alias istrue="test 0 -ne"
  28. alias isfalse="test 0 -eq"
  29.  
  30. # For each (machine,path) given, put the filename in filename[n]
  31. # and the machine it is on in machine[n].
  32. # (machine,path) may be given as machine:path or ftp://machine/path or
  33. # ftp://machine:23/path or ftp://machine:/path
  34. function SplitNames {
  35.     typeset file sourceMach=$LocalMach paths
  36.     typeset -i i=1
  37.  
  38.     set -A paths -- "$@"
  39.     [[ -n "$gethost" && "${paths[$#-1]}" != *:* ]] && sourceMach=$gethost
  40.  
  41.     unset filename[*] machine[*]
  42.     for file; do
  43.     case "$file" in
  44.     @(ftp|FTP)://*)
  45.         file=${file#???://}
  46.         machine[i]=${file%%?(:?(23))/*}
  47.         filename[i]=${file#*/}
  48.         ;;
  49.     *:*)
  50.         machine[i]=${file%%:*}
  51.         filename[i]=${file#*:}
  52.         ;;
  53.     *)
  54.         [ i -lt $# ] && machine[i]=$sourceMach || machine[i]=$LocalMach
  55.         filename[i]=$file
  56.         ;;
  57.     esac
  58.     let i+=1
  59.     done
  60. }
  61.  
  62. function verboseprint {
  63.     print -u1 "$*"
  64.     print -u2 "$*"
  65. }
  66.  
  67. function MakeDir {
  68.     OFS=$IFS
  69.     typeset IFS=/ dir component
  70.  
  71.     [[ $1 != /* ]] && dir=.
  72.     set -- $1
  73.     IFS=$OFS
  74.     for component; do
  75.     dir=$dir/$component
  76.     if [ ! -d "$dir" ]; then
  77.         if mkdir "$dir"; then :; else
  78.         print -u2 "Could not make directory $dir."
  79.         return 1
  80.         fi
  81.     fi
  82.     done
  83.     return 0
  84. }
  85.  
  86. function MakeRemoteDir {
  87.     OFS=$IFS
  88.     typeset IFS=/ dir component
  89.  
  90.     [[ $1 != /* ]] && dir=.
  91.     set -- $1
  92.     IFS=$OFS
  93.     for component; do
  94.     dir=$dir/$component
  95.     # Optimization: only create dir if we didn't last time
  96.     [[ "$dir" = "$lastdir" || "$lastdir" = "$dir/"* ]] ||
  97.     verboseprint mkdir $dir
  98.     done
  99.     lastdir=$dir
  100.     return 0
  101. }
  102.  
  103. # CopyFiles: issue ftp(TC) commands to copy files.
  104. # Usage: CopyFiles [sourcemachine:]sourcepath ... [destmachine:]destpath
  105. # An alternate way of specifying machine:path, for the sake of copied & pasted
  106. # URLs, is: ftp://machine/path
  107. # Global vars:
  108. # Uses LocalMach (should be name of local machine), defDest
  109. # Sets global arrs machine[]/filename[]
  110. function CopyFiles {
  111.     unset machine[*] filename[*]
  112.     [ -n "$defDest" ] && set -- "$@" "$defDest"
  113.     SplitNames "$@"    # split names into filename[1..n] and machine[1..n]
  114.     typeset DestMach=${machine[$#]}    # Machine to copy files to
  115.     typeset DestPath=${filename[$#]}     # Destination file/dir
  116.     unset machine[$#] filename[$#]
  117.     [ -z "$DestPath" ] && DestPath=.    # dest was given as machine:
  118.  
  119.     $debug && print -ru2 -- \
  120. "
  121. Source machines: ${machine[*]}
  122. Source files: ${filename[*]}
  123. Dest machine: $DestMach
  124. Dest path: $DestPath"
  125.     # Try to determine if destination should be a directory
  126.     # so that it can be forced to be a directory.
  127.     [[ $DestPath != */ &&    # don't add / if it already ends in /
  128.     ( $# -gt 2 ||    # if more than two args given, last must be a dir
  129.     # If dest in on local machine, we can check whether it is a directory
  130.     $DestMach = $LocalMach && -d $DestPath || 
  131.     # If dest ends with . or .., it is a directory
  132.     # If we are appending source path, dest must be a directory
  133.     $DestPath = ?(*/)@(.|..) ) ]] || $AddSourcePath && DestPath=$DestPath/
  134.  
  135.     # If one of the above tests made us think dest is a directory,
  136.     # but it isn't, complain
  137.     if [[ ( "$DestPath" = */ ) && 
  138.     ( "$DestMach" = $LocalMach ) && ! -d "$DestPath" ]];
  139.     then
  140.     print -u2 "Destination is not a directory."
  141.     exit
  142.     fi
  143.     DoCopy "$DestMach" "$DestPath"
  144. }
  145.  
  146. # Usage: OpenMachine machine-name
  147. # Emits login sequence or doesn't, depending on .netrc file and global
  148. # variables anon and noanon
  149. function OpenMachine {
  150.     typeset machine=$1 netrc=$HOME/.netrc user= password=
  151.  
  152.     [ "$machine" = "$lastmachine" ] && return 0
  153.     lastmachine=$machine
  154.  
  155.     # .netrc parsing is done as closely to ftp as possible.
  156.     # ftp processes .netrc as a stream of tokens separated by whitespace or ","
  157.     # Unfortunately standard awk can only have one record separator character,
  158.     # so must use FS, and has a limit on the number of fields per record, so
  159.     # must still deal with one line at a time even though newline is the same
  160.     # as the other separators as far as ftp is concerned.
  161.     if isfalse anon && [ -r $netrc ]; then
  162.     awk -F'[ \t,]' "-vmachine=$machine" -vdebug=$debug '
  163. BEGIN {
  164.     split("machine,default,login,password,passwd,account,macdef",e,",")
  165.     for (i = 1; i in e; i++)
  166.     names[e[i]]
  167.     debug = debug == "true"
  168.     if (debug)
  169.     printf "Reading .netrc; debugging is on\n" > "/dev/stderr"
  170. }
  171. function gotmach(m) {
  172.     if (gotMachName) {    # if there was a previous entry, process it
  173.     if (debug)
  174.         printf "Ending entry for machine <%s>\n",machName > "/dev/stderr"
  175.     if (machName == "" || machName == machine) {
  176.         if (debug)
  177.         printf "Found search machine <%s>\n",machName > "/dev/stderr"
  178.         if ("passwd" in Fields)
  179.         Fields["password"] = Fields["passwd"]
  180.         if ("login" in Fields && "password" in Fields)
  181.         print Fields["login"] " " Fields["password"]
  182.         gotMachName = 0
  183.         exit
  184.     }
  185.     }
  186.     if (debug)
  187.     printf "Starting entry for machine <%s>\n",m > "/dev/stderr"
  188.     split("",Fields)
  189.     gotMachName = 1
  190.     machName = m
  191. }
  192. function dotok(tok) {
  193.     if (debug)
  194.     printf "got token <%s>\n",tok > "/dev/stderr"
  195.     if (name == "") {
  196.     if (debug)
  197.         printf "Directive is <%s>\n",tok > "/dev/stderr"
  198.     if (tok == "default")
  199.         gotmach("")
  200.     else if (tok in names)
  201.         name = tok
  202.     else if (debug)
  203.         printf "Unrecognized directive <%s>\n",tok > "/dev/stderr"
  204.     }
  205.     else if (name == "macdef") {
  206.     if (debug)
  207.         printf "In macro definition <%s>\n",tok > "/dev/stderr"
  208.     continue
  209.     }
  210.     else {
  211.     if (debug)
  212.         printf "Directive <%s> gets value <%s>\n",name,tok > "/dev/stderr"
  213.     if (name == "machine")
  214.         gotmach(tok)
  215.     else
  216.         Fields[name] = tok
  217.     name = ""
  218.     }
  219. }
  220. {
  221.     if (debug)
  222.     printf "Processing line: %s\n",$0 > "/dev/stderr"
  223.     if (name == "macdef") {
  224.     if ($0 ~ /^[ \t\n,]*$/)    # end of macdef
  225.         name = ""
  226.     if (debug)
  227.         printf "Macro text: %s\n",$0 > "/dev/stderr"
  228.     }
  229.     else
  230.     for (i = 1; i <= NF; i++)
  231.         dotok($i)
  232. }
  233. END {
  234.     if (gotMachName)
  235.     gotmach("END OF FILE")
  236.     if (debug)
  237.     printf "Finished processing .netrc\n" > "/dev/stderr"
  238. }
  239. ' $netrc | read user password
  240.     fi
  241.     if [ -z "$password" ]; then
  242.     if istrue noanon; then
  243.         print -u2 "No .netrc entry for machine $machine"
  244.         exit 1
  245.     fi
  246.     user=anonymous
  247.     password=$USER@${LocalMach%%.}
  248.     fi
  249.     verboseprint open $machine
  250.     print -u2 "user $user *******"
  251.     print "user $user $password"
  252. }
  253.  
  254. # Usage: DoCopy destination-machine destination-path
  255. # Copies the files in global arrs machine[]/filename[] to the given dest
  256. # Global vars:
  257. # Uses machine[], filename[], LocalMach, check
  258. function DoCopy {
  259.     typeset DestMach=$1
  260.     typeset DestPath=$2
  261.     typeset OpenMach    # Machine that connection is currently open to
  262.     typeset SourceMach SourceFile
  263.     typeset -i i=1
  264.     typeset FileName
  265.  
  266.     while [ i -le ${#machine[*]} ]; do
  267.     istrue check && verboseprint "runique"
  268.  
  269.     SourceMach=${machine[i]}
  270.     SourceFile=${filename[i]}
  271.  
  272.     DestFile=$DestPath
  273.     if $PreCWD; then
  274.         [[ "$DestFile" != /* ]] && DestFile=$PWD/$DestFile
  275.         [[ "$SourceFile" != /* ]] && SourceFile=$PWD/$SourceFile
  276.     fi
  277.     # if DestPath is a dir, add source filename to it without source path
  278.     if [[ "$DestFile" = */ ]]; then
  279.         $AddSourcePath && DestFile=$DestFile$SourceFile ||
  280.         DestFile=$DestFile${SourceFile##*/}
  281.     fi
  282.  
  283.     if [ $SourceMach = $LocalMach ]; then
  284.         if [ $DestMach != "$OpenMach" ]; then
  285.         OpenMachine $DestMach
  286.         OpenMach=$DestMach
  287.         fi
  288.         if istrue createdirs; then
  289.         MakeRemoteDir "${DestFile%/*}"
  290.         fi
  291.         verboseprint put $SourceFile $DestFile
  292.     elif [ $DestMach = $LocalMach ]; then
  293.         if istrue check && [ -f "$DestFile" ]; then
  294.         print -u2 "$DestFile already exists."
  295.         continue
  296.         fi
  297.         # If destination is on local machine,
  298.         # the dest will be a full dir/filename
  299.         if istrue createdirs; then
  300.         MakeDir "${DestFile%/*}" || continue
  301.         fi
  302.         if [ $SourceMach != "$OpenMach" ]; then
  303.         OpenMachine $SourceMach
  304.         OpenMach=$SourceMach
  305.         fi
  306.         # If source filename has wildcards ([, ], *, ?) do an mget
  307.         if [[ "$SourceFile" = *[][*?]* ]]; then
  308. #        verboseprint lcd "$DestFile"
  309.         verboseprint mget "$SourceFile"
  310. #        verboseprint lcd $PWD
  311.         else
  312.         verboseprint get "$SourceFile" "$DestFile"
  313.         dests[destct]=$DestFile
  314.         let destct+=1
  315.         fi
  316.     else
  317.         print -u2  "Neither source machine \"$SourceMach\" "\
  318. "nor destination machine \"$DestMach\" is local."
  319.     fi
  320.     let i+=1
  321.     done
  322. }
  323.  
  324. # This has to be something that actually reads all of its input, so that
  325. # the output process won't get SIGPIPE
  326. function discard {
  327.     typeset line
  328.     while read line; do
  329.     :
  330.     done
  331. }
  332.  
  333. ### Start of main program
  334. nam=${0##*/}
  335. AddSourcePath=false
  336. PreCWD=false
  337. do_l=false
  338. pairs=false
  339. debug=false
  340.  
  341. typeset -i check=0 createdirs=0 readinput=0 anon=0 noanon=0 destct=0
  342. typeset gethost= ftp="ftp -in"
  343.  
  344. while getopts :cdflnrswg:htD:Lpx Option
  345. do
  346.     case "$Option" in
  347.     h)
  348.     print -r -- \
  349. "$nam: do ftp transfers using rcp-style parameters.
  350. Usage: $nam [-cdtfhlnrswL] [-g<host>] [-D<dest>] <source> [<source> ...] <dest>
  351. At least one of <source> and <destpath> must be the local system.
  352. A remote filename is given as machinename:filename or ftp://hostname/filename
  353. If remote filenames contain wildcards, they will be globbed on the remote
  354. machine.  Make sure they are quoted when $nam is invoked.
  355. If the invoking user's .netrc file (see ftp(TC)) contains an entry for the
  356. remote system (or a \"default\" entry) with a login and password supplied, $nam
  357. will log in using the given login and password.  If not, $nam will login in as
  358. user anonymous and with the user@localsystem as the password.
  359. Files are transferred into the local directory or into the named remote
  360. directory regardless of the path to the source file.
  361. Options:
  362. -c: check: do not overwrite files.
  363. -d: create directories as needed.
  364. -D<dest>: All non-option arguments are taken to be source files and are copied
  365.     to destination <dest>.  This lets the destination be given as the first
  366.     argument intead of the last, and also lets input lines given for the -r
  367.     option consist of source filenames only.
  368. -p: Each pair of filenames given on the command line is taken to be a source
  369.     and destination pair.  For example, \"$nam a b c d e f\" is equivalent to
  370.     invoking $nam three time separately, with a b, c d, and e f as arguments.
  371. -t: Tell what commands would be issued to ftp without actually doing it.
  372. -f: force: overwrite files (default).
  373. -g<host>: If the destination is on the local host (does not contain a ':'), all
  374.     source files that do not have a host name specified (do not contain a ':')
  375.     are assumed to exist on <host>.
  376. -h: print this help.
  377. -l: fail if there is no entry with login and password for the remote system,
  378.     instead of logging in as anonymous.
  379. -L: After transferring allfiles, do an 'ls -l' on any destination files that
  380.     are on the local host.  Files copied using wildcards are skipped.
  381. -n: log in as anonymous even if there is an entry for the remote system in
  382.     the user's .netrc file.
  383. -r: read lists of source files and destination from the standard input, one
  384.     set per line, and copy files accordingly.  If -D is given, input lines may
  385.     consist of single filenames.
  386. -s: Append the entire pathname of the source file to the destination for
  387.     each file copied.   If no destination path is given, '.' is used, so the
  388.     destination will become relative to the current directory (if the
  389.     destination is the local) or the ftp login directory (if the destination
  390.     is remote).
  391. -w: Prepend the current working directory to any of the source files and
  392.     destination that do not begin with a '/'."
  393.     exit 0;;
  394.     g) gethost=$OPTARG;;
  395.     t) ftp=discard;;
  396.     c) check=1;;
  397.     d) createdirs=1;;
  398.     D) 
  399.     defDest=$OPTARG
  400.     if [ -z "$defDest" ]; then
  401.         print -u2 -- "$nam: Value given with -D must be non-null."
  402.         exit 1
  403.     fi
  404.     ;;
  405.     f) check=0;;
  406.     l) noanon=1;;
  407.     L) do_l=true;;
  408.     p) pairs=true;;
  409.     n) anon=1;;
  410.     r) readinput=1;;
  411.     s) AddSourcePath=true;;
  412.     w) PreCWD=true;;
  413.     x) debug=true;;
  414.     :) 
  415.     print -r -u2 -- \
  416.     "$nam: Option '$OPTARG' requires a value.  Use -h for help."
  417.     exit 1
  418.     ;;
  419.     \?) echo "$OPTARG: invalid option."; exit 1;;
  420.     esac
  421. done
  422.  
  423. shift $((OPTIND-1))
  424. if isfalse readinput && [ $# -lt 2 -a -z "$defDest" ]; then
  425.     print -u2 "$nam: Not enough arguments.  Use -h for help."
  426.     exit 1
  427. fi
  428.  
  429. if $pairs && [ $(($#%2)) -ne 0 ]; then
  430.     print -ru2 -- "Must give an even number of filenames with -p."
  431.     exit 1
  432. fi
  433.  
  434. LocalMach=`hostname` 
  435.  
  436. # In order to be able to reference dests[] and destct at the end of the
  437. # program, we must keep CopyFiles in the context of the parent process.
  438. # Doing a simple pipe into $ftp puts CopyFiles in a separate process, so
  439. # instead start it as a coprocess and then write into the coprocess pipe.
  440. $ftp >&2 |&
  441. if istrue readinput; then
  442.     while read line; do
  443.     CopyFiles $line
  444.     done
  445. elif $pairs; then
  446.     while [ $# -gt 0 ]; do
  447.     CopyFiles "$1" "$2"
  448.     shift 2
  449.     done
  450. else
  451.     CopyFiles "$@"
  452. fi >&p
  453. wait    # for ftp to finish
  454. $do_l && [ destct -gt 0 ] && l -- "${dests[@]}"
  455.